<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://upyun.luckly-mjw.cn/lib/mux-mp4.js"></script>
</head>

<body>
  <div id="videoBox"></div>

  <button id="btnStart">Start</button>
  <button id="btnDownload">Download</button>
  <script>
    var videoUrl = 'https://m.media-amazon.com/images/S/vse-vms-transcoding-artifact-us-east-1-prod/bda11534-a8e7-4366-a39a-a88c48817c27/default.jobtemplate.hls360.m3u8';
    console.log('videoUrl', videoUrl);

    document.querySelector('#btnStart').addEventListener('click', start);
    document.querySelector('#btnDownload').addEventListener('click', download);

    async function start() {
      m3u8ToMp4Blob(videoUrl).then(fileBlob => {
        const url = URL.createObjectURL(fileBlob);
        document.querySelector('#videoBox').innerHTML =
          `<video src="${url}" controls></video>`;
      });
    }

    async function download() {
      m3u8ToMp4Blob(videoUrl).then(fileBlob => {
        const url = URL.createObjectURL(fileBlob);
        const downloadLink = document.createElement('a');
        downloadLink.href = url;
        downloadLink.download = 'my.mp4';
        downloadLink.click();
      });
    }

    async function m3u8ToMp4Blob(videoUrl) {
      function getBaseUrl(url) {
        return url.substring(0, url.lastIndexOf('/') + 1);
      }

      function getDuration(s) {
        let n = 0;
        s.split('\n').forEach(item => {
          if (item.toUpperCase().indexOf('#EXTINF:') > -1) {
            n += parseFloat(item.split('#EXTINF:')[1]);
          }
        });
        return n;
      }

      function getTsUrlList(s, baseUrl) {
        let ls = s.match(/[^\s\r\n]+\.ts(\?[^\s\r\n]*)?/g);
        return ls.map(tsPath => {
          if (tsPath.startsWith('http')) {
            return tsPath;
          }
          return joinUrl(tsPath, baseUrl);
        });
      }

      function joinUrl(targetURL, baseUrl) {
        if (targetURL.indexOf('http') === 0) {
          if (location.href.indexOf('https') === 0) {
            return targetURL.replace('http://', 'https://');
          }
          return targetURL;
        }
        if (targetURL[0] === '/') {
          let domain = baseUrl.split('/');
          return domain[0] + '//' + domain[2] + targetURL;
        }
        let domain = baseUrl.split('/');
        domain.pop();
        return domain.join('/') + '/' + targetURL;
      }

      async function getTsList(list) {
        const rc = [];
        for (let i = 0; i < list.length; i++) {
          const address = list[i];
          const ab = await fetch(address).then(res => res.arrayBuffer());
          if (!ab) {
            throw new Error('Load ts error');
          }
          rc.push(ab);
        }
        return rc;
      }

      async function getMp4FragList(list, duration) {
        const rc = [];
        for (let i = 0; i < list.length; i++) {
          const ab = list[i];
          const mp4 = await convertMp4(ab, i, duration);
          if (!mp4) {
            throw new Error('convertMp4 error');
          }
          rc.push(mp4);
        }
        return rc;
      }

      function convertMp4(data, index, duration) {
        return new Promise((resolve, reject) => {
          const transmuxer = new muxjs.Transmuxer({
            keepOriginalTimestamps: true,
            duration,
          });
          transmuxer.on('data', segment => {
            if (index === 0) {
              let data = new Uint8Array(segment.initSegment.byteLength + segment.data.byteLength);
              data.set(segment.initSegment, 0);
              data.set(segment.data, segment.initSegment.byteLength);
              resolve(data.buffer)
            } else {
              resolve(segment.data)
            }
          })
          transmuxer.push(new Uint8Array(data));
          transmuxer.flush();
        });
      }

      const text = await fetch(videoUrl).then(res => res.text());
      const baseUrl = getBaseUrl(videoUrl);
      const tsUrlList = getTsUrlList(text, baseUrl);
      const tsList = await getTsList(tsUrlList);
      const duration = getDuration(text);
      const mp4FragList = await getMp4FragList(tsList, duration);
      const fileBlob = new Blob(mp4FragList, { type: 'video/mp4' });

      console.log({
        // text,
        // duration,
        // baseUrl,
        // tsUrlList,
        // tsList,
        // mp4FragList
        fileBlob,
      });
      return fileBlob;
    }

  </script>
</body>

</html>